#ifndef QD3DUTILS_H
#define QD3DUTILS_H
#include "QD3DShaderDesc.h"
#include "QD3DContext.h"
#include <QVector>

#ifndef SAFE_RELEASE
#define SAFE_RELEASE(p)      { if (p) { (p)->Release(); (p)=NULL; } }
#endif
#ifndef SAFE_DELETE
#define SAFE_DELETE(p)      { if (p) { delete (p); (p)=NULL; } }
#endif

#ifndef CONST_STRUCT_BEGIN
#define CONST_STRUCT_BEGIN(__struct__name__) \
	struct __struct__name__ \
	{ \
		__struct__name__ () \
		{ \
			ZeroMemory (unused, sizeof (unused)); \
		} \
		struct Var {
#define CONST_STRUCT_END \
		} var; \
		UINT unused[4 - sizeof (Var) / 4 % 4]; \
	};
#endif

class QD3DUtils
{
public:

	enum Shader { VERTEX_SHADER, PIXEL_SHADER, GEOMETRY_SHADER, COMPUTE_SHADER };
	QD3DUtils (QD3DContext *pContext = QD3DContext::cur ());
	~QD3DUtils ();

	void setViewport (float left, float top,
			float width, float height,
			float minDepth = 0.0f, float maxDepth = 1.0f);
	void setViewports (const QVector< CD3D11_VIEWPORT > &viewports);

	ID3D11SamplerState *createSamplerState (const CD3D11_SAMPLER_DESC &desc);
	ID3D11Query *createQuery (D3D11_QUERY queryType, UINT MiscFlags = 0);
	void beginDataCollection (ID3D11Asynchronous *pAsync);
	void endDataCollection (ID3D11Asynchronous *pAsync);
	bool getDataCollected (ID3D11Asynchronous *pAsync, void *pData = NULL,
			UINT size = 0, bool waitForComplete = true);
	/************************************************************************/
	/* Basic Drawing Functions                                              */
	/************************************************************************/

	void clearRenderTarget (float r, float g, float b, float a = 1.0);
	void clearDepthStencil (float depth = 1.0f, float stencil = 0.0f);
	void setShaders (ID3D11VertexShader *pVertexShader,
			ID3D11GeometryShader *pGeometryShader,
			ID3D11PixelShader *pPixelShader);
	void setPrimitiveTopology (D3D11_PRIMITIVE_TOPOLOGY topology);
	void draw (UINT vertexCount, UINT startLocation);
	void drawIndexed (UINT indexCount, UINT startLocation,
			UINT baseVertexLocation);
	void swapBuffers ();

	/************************************************************************/
	/* Compute Shader Functions                                             */
	/************************************************************************/

	void setComputeShader (ID3D11ComputeShader *pShader);
	void dispatch (UINT threadGroupCountX, UINT threadGroupCountY,
			UINT threadGroupCountZ);
	void indirectDispatch (ID3D11Buffer *pArgBuffer, UINT offset);

	/************************************************************************/
	/* Object For Shader Functions                                          */
	/************************************************************************/

	ID3D11ShaderResourceView *createSRV (
			const CD3D11_SHADER_RESOURCE_VIEW_DESC &desc,
			ID3D11Resource *pResource);
	ID3D11ShaderResourceView *createSRV (ID3D11Resource *pResource);
	CD3D11_SHADER_RESOURCE_VIEW_DESC createDefaultSRVDesc (
			ID3D11Resource *pResource);
	ID3D11UnorderedAccessView *createUAV (
			const CD3D11_UNORDERED_ACCESS_VIEW_DESC &desc,
			ID3D11Resource *pResource);
	ID3D11UnorderedAccessView *createUAV (ID3D11Resource *pResource);
	CD3D11_UNORDERED_ACCESS_VIEW_DESC createDefaultUAVDesc (
			ID3D11Resource *pResource);
	void setSRVs (Shader shaderType, UINT startSlot,
			const QVector< ID3D11ShaderResourceView * > &viewPtrs);
	void clearSRVs (Shader shaderType, UINT startSlot, UINT count);
	void setUAVs (Shader shaderType, UINT startSlot,
			const QVector< ID3D11UnorderedAccessView * > &viewPtrs);
	void clearUAVs (Shader shaderType, UINT startSlot, UINT count);
	void setConstBufs (Shader shaderType, UINT startSlot,
			const QVector< ID3D11Buffer * > &pConstBuffers);
	void clearConstBufs (Shader shaderType, UINT startSlot, UINT count);
	void setSamplers (Shader shaderType, UINT startSlot,
			const QVector< ID3D11SamplerState * > &pSamplers);
	void clearSamplers (Shader shaderType, UINT startSlot, UINT count);

	/************************************************************************/
	/*  Create Resource Functions                                           */
	/************************************************************************/

	ID3D11Buffer *createBuffer (const CD3D11_BUFFER_DESC &bufferDesc,
			VOID *pData = NULL, const std::string &debugName = "");
	template< class T >
	ID3D11Buffer *createConstBuffer (T *pData = NULL,
			const std::string &debugName = "");
	template< class T >
	ID3D11Buffer *createStructuredBuffer (UINT elemCount,
			const T *pInitData, UINT bindFlags,
			const std::string &debugName = "");
	ID3D11Buffer *createStructuredBuffer (UINT elemCount, UINT structSize,
			VOID *pInitData, UINT bindFlags,
			const std::string &debugName = "");
	ID3D11Buffer *createRawBuffer (UINT byteSize, VOID* pInitData,
			UINT bindFlags, const std::string &debugName = "");

	ID3D11Texture2D *createTexture2D (const CD3D11_TEXTURE2D_DESC &texDesc,
			const QVector< D3D11_SUBRESOURCE_DATA > &subresourceDataList =
			QVector< D3D11_SUBRESOURCE_DATA > ());

	/************************************************************************/
	/*  GPU Access Functions                                                */
	/************************************************************************/

	void copyResource (ID3D11Resource *pDstResource, ID3D11Resource *pSrcResource,
			INT dstSubresource = -1, INT srcSubresource = 0,
			INT dstX = 0, INT dstY = 0, INT dstZ = 0,
			const D3D11_BOX &srcBox = D3D11_BOX());
	void updateSubresource (ID3D11Resource *pResource, UINT dst,
			const void *pSrcData, const D3D11_BOX *pDstBox = NULL,
			UINT rowPitch = 0, UINT depthPitch = 0);
	void map (D3D11_MAP mapType, ID3D11Resource *pDstResource,
			D3D11_MAPPED_SUBRESOURCE *pSubResource,
			UINT subResourceIndex = 0);
	void unmap (ID3D11Resource *pDstResource, UINT subResourceIndex = 0);

	/************************************************************************/
	/*  Shader Utility Functions                                            */
	/************************************************************************/
	template< class T >
	T *createShaderFromFile (Shader shaderType, char *shaderProfile,
			const QString &fileName, const QString &entryPoint,
			QD3DShaderDesc &shaderDesc = QD3DShaderDesc (),
			ID3DBlob **ppShaderBlob = NULL);
	ID3DBlob *compileShaderFromFile (const QString &fileName,
			const QString &entryPoint, char *shaderProfile,
			QD3DShaderDesc &shaderDesc = QD3DShaderDesc ());

	/************************************************************************/
	/* IA Stage Functions                                                   */
	/************************************************************************/

	ID3D11InputLayout *createInputLayout (
			const QVector< D3D11_INPUT_ELEMENT_DESC > &inputLayoutDescs,
			ID3DBlob *pShaderBlob);
	void setVertexBuffers (UINT startSlot,
			const QVector< ID3D11Buffer * > &pVertexBuffers,
			const QVector< UINT > &elemBytesArr,
			const QVector< UINT > &elemOffsetArr);
	void clearVertexBuffers (UINT startSlot, UINT num);
	void setIndexBuffer (ID3D11Buffer *pIndexBuffer, DXGI_FORMAT format,
			UINT offset);
	void clearIndexBuffer ();
	void setInputLayout (ID3D11InputLayout *pInputLayout);

	/************************************************************************/
	/* OM Stage Functions                                                   */
	/************************************************************************/
	CD3D11_BLEND_DESC createTransparentBlendDesc ();
	ID3D11BlendState *createBlendState (const CD3D11_BLEND_DESC &blendDesc);
	void setBlendState (ID3D11BlendState *pBlendState,
			float *factors = NULL, UINT mask = D3D11_COLOR_WRITE_ENABLE_ALL);
private:

	QD3DContext *__p_context;

};

/************************************************************************/
/* Template Functions Implementation                                    */
/************************************************************************/

template< class T >
ID3D11Buffer * QD3DUtils::createConstBuffer (T *pData, const std::string &debugName)
{
	CD3D11_BUFFER_DESC desc (sizeof (T), D3D11_BIND_CONSTANT_BUFFER);
	return createBuffer (desc, pData, debugName);
}

template< class T >
ID3D11Buffer * QD3DUtils::createStructuredBuffer (UINT elemCount,
		const T *pInitData, UINT bindFlags, const std::string &debugName)
{
	CD3D11_BUFFER_DESC desc;
	ZeroMemory (&desc, sizeof (CD3D11_BUFFER_DESC));
	desc.BindFlags = bindFlags;
	desc.ByteWidth = elemCount * sizeof (T);
	desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
	desc.StructureByteStride = sizeof (T);
	return createBuffer (desc, (void *)pInitData, debugName);
}

template< class T >
T *QD3DUtils::createShaderFromFile (Shader shaderType,
		char *shaderProfile, const QString &fileName,
		const QString &entryPoint, QD3DShaderDesc &shaderDesc,
	ID3DBlob **ppShaderBlob)
{
	ID3DBlob *pShaderBlob = compileShaderFromFile (fileName, entryPoint,
		shaderProfile, shaderDesc);
	HRESULT hr;
	T *pShader;
	switch (shaderType) {
	case VERTEX_SHADER:
		hr = __p_context->pDevice->CreateVertexShader (
				pShaderBlob->GetBufferPointer(), pShaderBlob->GetBufferSize(),
				NULL, (ID3D11VertexShader **)&pShader);
		break;
	case PIXEL_SHADER:
		hr = __p_context->pDevice->CreatePixelShader (
				pShaderBlob->GetBufferPointer(), pShaderBlob->GetBufferSize(),
				NULL, (ID3D11PixelShader **)&pShader);
		break;
	case GEOMETRY_SHADER:
		hr = __p_context->pDevice->CreateGeometryShader (
				pShaderBlob->GetBufferPointer(), pShaderBlob->GetBufferSize(),
				NULL, (ID3D11GeometryShader **)&pShader);
		break;
	case COMPUTE_SHADER:
		hr = __p_context->pDevice->CreateComputeShader (
				pShaderBlob->GetBufferPointer(), pShaderBlob->GetBufferSize(),
				NULL, (ID3D11ComputeShader **)&pShader);
		break;
	}
	if (FAILED (hr)) {
		pShaderBlob->Release ();
		throw std::exception ("QD3DUtils::createVertexShaderFromFile(): "
				"fail to create vertex shader object.");
	}
	if (ppShaderBlob != NULL)
		*ppShaderBlob = pShaderBlob;
	else
		SAFE_RELEASE (pShaderBlob);

// #if defined(DEBUG) || defined(_DEBUG)
// 	pShader->SetPrivateData (WKPDID_D3DDebugObjectName,
// 			entryPoint.size () + 1, entryPoint.toStdString ().c_str ());
// #endif

	return pShader;
}

#endif // QD3DUTILS_H